package logic.algorithm.aco;

import java.util.ArrayList;
import javax.swing.JFrame;
import logic.objects.Node;
import logic.objects.Problem;

public abstract class AcoMetaHeuristic extends Thread {

	protected ArrayList<Ant> ants;
	protected Problem problem;
	protected JFrame frame;
	private boolean terminated;
	private Double bestTourSoFarValue = Double.MAX_VALUE;
	private ArrayList<Node> bestTourSoFar;
	protected Double tau0;

	public AcoMetaHeuristic(Problem problem, JFrame frame) {
		readInstance(problem);
		this.frame = frame;
		}

	public void run() {
		synchedRun();
	}
	
	protected synchronized void synchedRun(){
		initializeProblem(problem);
		while (!terminated && !terminate()) {
			constructAntSolutions();
			daemonAction();
			updateStatistics();
			updatePheromon();
			frame.repaint();
		}
	}
	protected void initializeProblem(Problem problem) {
		computeDistances();
		initializeAnts();
		initializeParameters();
		initializePheromon();
		initializeStatistics();
		updateBestTourSoFar();
	}

	private void initializePheromon() throws Tau0NotImplementedException {
		if (tau0 == null)
			throw (new Tau0NotImplementedException());
		else {
			problem.getConnections().initializePheromon(tau0);
		}
	}

	protected abstract void initializeStatistics();

	private void computeDistances() {
		problem.applyConnections();
	}

	protected void constructAntSolutions() {
		for (int k = 0; k < ants.size(); k++)
			for (int i = 0; i < problem.getNodes().size(); i++)
				ants.get(k).setVisited(i, false);
		int step = 0;
		int randomNode;
		for (int k = 0; k < ants.size(); k++) {
			randomNode = (int) (Math.random() * problem.getNodes().size());
			ants.get(k).setTourInstanze(step, randomNode);
		}
		while (++step < problem.getNodes().size()) {
			for (int k = 0; k < ants.size(); k++)
				makeDecision(k, step);
		}
		for (int k = 0; k < ants.size(); k++) {
			ants.get(k).setTourInstanze(problem.getNodes().size(),
					ants.get(k).getTourInstanze(1));
			ants.get(k).setTourLength(computeLength(k));
		}
	}

	protected double computeLength(int k) {
		Ant ant = ants.get(k);
		double length = 0.0;
		for (int i = 0; i < problem.getNodes().size() - 1; i++) {
			length += problem.getConnections()
					.get(ant.getTourInstanze(i), ant.getTourInstanze(i + 1))
					.getLength();
		}
		length += problem
				.getConnections()
				.get(ant.getTourInstanze(problem.getNodes().size() - 1),
						ant.getTourInstanze(0)).getLength();
		return length;
	}

	/**
	 * Die Arraylist muss erstellt und eine bestimmte Anzahl an Ants generiert
	 * werden.
	 */
	protected abstract void initializeAnts();

	/**
	 * Die Parameter werden als globale Variablen angelegt und müssen in dieser
	 * Methode inizialisiert werden.
	 */
	protected abstract void initializeParameters();

	/**
	 * Es muss ein Wahrscheinlichkeitsmodell hinterlegt werden, dass
	 * anschließend einen zusätzlichen Knoten die Methode
	 * {@link Ant#setTourInstanze(int, Integer)} aufruft.
	 * 
	 * @param k
	 * @param step
	 */
	protected abstract void makeDecision(int k, int step);

	/**
	 * Zusaetzliche Aktionen, die nach der Lösungsfindung ausgeführt werden
	 * sollen.
	 */
	protected abstract void daemonAction();

	/**
	 * Methode die die Pheromonwerte der Connections updatet.
	 */
	protected abstract void updatePheromon();

	protected void updateStatistics() {
		if (getBestSolutionsCost() < bestTourSoFarValue) {
			setBestTourSoFarValue(getBestSolutionsCost());
			setBestTourSoFar(getBestSolution());
			updateBestTourSoFar();
		}
	}

	/**
	 * Terminierungsbedingung
	 * 
	 * @return true, wenn der Algorithmus terminiert werden soll
	 */
	protected abstract boolean terminate();

	/**
	 * @return Gibt die Kosten der besten Lösung der Iteration zurück
	 */
	public abstract double getBestSolutionsCost();

	/**
	 * @return Gibt die beste Lösung der Iteration zurück
	 */
	public abstract ArrayList<Node> getBestSolution();

	private void readInstance(Problem problem) {
		this.problem = problem;
	}

	public void setTerminated() {
		terminated = true;
	}

	public ArrayList<Node> getBestTourSoFar() {
		return bestTourSoFar;
	}

	private void setBestTourSoFar(ArrayList<Node> bestTourSoFar) {
		this.bestTourSoFar = bestTourSoFar;
	}

	public Double getBestTourSoFarValue() {
		return bestTourSoFarValue;
	}

	private void setBestTourSoFarValue(Double bestTourSoFarValue) {
		this.bestTourSoFarValue = bestTourSoFarValue;
	}

	private void updateBestTourSoFar() {
		problem.setBestTourSoFarValue(getBestTourSoFarValue());
		problem.setBestTourSoFar(getBestTourSoFar());
	}

}
